#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <arpa/inet.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include "link_socket.h"
#include "zlog.h"
#include "config.h"


linkSigInfo_t   gLinkSigInfo      = {0};
linkList_t      *gClientListHead  = NULL;
linkList_t       *gServerLinkHead = NULL;
pthread_mutex_t mutexGLinkSigInfo;

static int sock_listen(int port_ser, char *ip_ser)
{
	int ret = 0;
	int fd_ser = -1;
	struct sockaddr_in addr_ser = {0};
	int on = 1;

	memset(&addr_ser, 0, sizeof(addr_ser));
	addr_ser.sin_family = AF_INET;
	addr_ser.sin_port = htons(port_ser);
	addr_ser.sin_addr.s_addr = !ip_ser ? htonl(INADDR_ANY) : inet_addr(ip_ser);

    fd_ser = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (fd_ser <= 0) {
        zlog_error(gZlog, "Failed to create TCP socket, ret[%d]", fd_ser);
        return fd_ser;
	}

	ret = setsockopt(fd_ser, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
	if (ret < 0) {
		zlog_error(gZlog, "Failed to set socket SO_REUSEADDR, ret[%d]", ret);
        close(fd_ser);
		return ret;
	}

    ret = bind(fd_ser, (struct sockaddr *)&addr_ser, sizeof(struct sockaddr)); 
	if (ret < 0) {
		zlog_error(gZlog, "Failed to bind socket to [%s:%d], ret[%d]", ip_ser, port_ser, ret);
        close(fd_ser);
		return ret;
	}

    ret = listen(fd_ser, 10);
    if (ret < 0) {
		zlog_error(gZlog, "Failed to listen to [%s:%d], ret[%d]", ip_ser, port_ser, ret);
        close(fd_ser);
        return ret;
    }

	return fd_ser;
}

static int sock_accept(int fd_ser, char *ip_cli, int bufflen, int *port_cli)
{
	struct sockaddr_in addr_cli = {0};
    socklen_t len = sizeof(addr_cli);
    int fd_cli = -1;

    fd_cli = accept(fd_ser, (struct sockaddr *)&addr_cli, &len);
    if (fd_cli < 0) {
        zlog_error(gZlog, "Failed to accept client, ret[%d]", fd_cli);
        return fd_cli;
    }
    if (ip_cli)
        snprintf(ip_cli, bufflen, "%s", inet_ntoa(addr_cli.sin_addr));

    if (port_cli)
        *port_cli = ntohs(addr_cli.sin_port);

    return fd_cli;
}

static int sock_read(void *id, void *buff, int len)
{
    int ret = -1;
    int left = len;
    fd_set fds;
    SenLink *link = (SenLink *)id;
    SOCKET sock = 0;
    struct timeval tv = {.tv_sec = 5, .tv_usec = 0};

    if (!id || !buff || len <= 0) {
        zlog_error(gZlog, "Invalid param, id[%p], buff[%p], len[%d]!", id, buff, len);
        return -EINVAL;
    }

    link->ret = 0;
    sock = link->sen_socket;
    if (sock < 0) {
        zlog_error(gZlog, "Invalid socket[%d]", sock);
        link->ret = -EINVAL;
        return -EINVAL;
    }
    memset(buff, 0, len);
    FD_ZERO(&fds);
    FD_SET(sock, &fds);

    while (left) {
        if (!FD_ISSET(sock, &fds)) {
            zlog_debug(gZlog, "Sock[%d] is not setted", sock);
            continue;
        }else{
            ret = select(sock + 1, &fds, NULL, NULL, &tv);
            if (ret < 0) {
                zlog_error(gZlog, "socket[%d] Failed to call select, err[%d]", sock, ret);
                link->ret = ret;
                return ret;
            }
            /* timeout when calling select() */
            if (ret == 0){
                return len - left;
            }
            ret = recv(sock, buff, left, 0);
            if (ret < 0) {
                zlog_error(gZlog, "Failed to read [%d]bytes from sock[%d], err[%d]!",
                        left, sock, errno);
                link->ret = ret;
                return ret;
            }
            if (ret == 0) {
                usleep(1000);/* Ensure that the connection is disconnected */
                pthread_mutex_lock(&mutexGLinkSigInfo);
                gLinkSigInfo.linkSigType = deletLinkSig;
                gLinkSigInfo.client.linkFd = link->chainId;
                pthread_mutex_unlock(&mutexGLinkSigInfo);
                link->ret = -EPIPE;
                FD_CLR(sock, &fds);
                FD_ZERO(&fds);
                return -EPIPE;
            }
            left -= ret;
            buff = (char *)buff + ret;
        }
    }/* end while */
    return len;
}

static int sock_write(void *id, void *buff, int len)
{
    int ret = 0;
    SenLink *link = (SenLink *)id;
    SOCKET sock = 0;

    if (!id || !buff || len <= 0) {
        zlog_error(gZlog, "Invalid param, id[%p], buff[%p], len[%d]!", id, buff, len);
        return -EINVAL;
    }

    link->ret = 0;
    sock = link->sen_socket;
    if (sock < 0) {
        zlog_error(gZlog, "Invalid socket[%d]", sock);
        link->ret = -EINVAL;
        return -EINVAL;
    }

    ret = send(sock, buff, len, 0);
    if (ret < 0) {
        zlog_error(gZlog, "Failed to send [%d]bytes to sock[%d], err[%d]!",
                len, sock, errno);
        link->ret = ret;
        usleep(1000);/* Ensure that the connection is disconnected */
        pthread_mutex_lock(&mutexGLinkSigInfo);
        gLinkSigInfo.linkSigType = deletLinkSig;
        gLinkSigInfo.client.linkFd = link->chainId;
        pthread_mutex_unlock(&mutexGLinkSigInfo);
        link->ret = -EPIPE;
        return ret;
    }

    return ret;
}


int recv_and_parse(ProChan *chan)
{
    int ret = -1;
    struct frame_list *frames = NULL;
    SenLink *link = (SenLink *)chan->id;
    zlog_info(gZlog, "Link Chan Created chanId[%d]", link->chainId);
    while (true) {
        frames = NULL;
        ret = proFramesRecv(chan, &frames);
        if (ret < 0 || !frames) {
            continue;
        }else{
            ret = proFramesParse(chan, &frames);
            if (ret < 0) {
                zlog_error(gZlog, "Failed to parse frame, ret[%d], frames[%p]",ret , frames);
            }
        }
        // zlog_info(gZlog, "Link Chan chanId[%d] alived", link->chainId);
    }/* end while(1) */
    return 0;
}

int commBindCliPth(linkDes_t *link)
{
    int ret = -1;
    link->link.ret = 0;
    link->link.sen_socket = link->linkFd;
    link->link.chainId = link->linkFd;
    zlog_debug(gZlog, "linkFd[%d]", link->linkFd);
    /* communication establish */
    ret = proChanInit(&link->chan, link->linkFd, &link->link, sock_read, sock_write, link->proParseExe,
                        DCM_PROD_TYPE, DCM_PROD_VER);
    if (ret < 0) {
        zlog_error(gZlog, "Failed to initialize isvision protocol library! ret[%d]", ret);
        close(link->linkFd);
    }
    /* heat beat to client when connect */
    /*
    ret = hearBeatRespo(&client->chan, op1_hB2Cil, 0, NULL, 0);
    if(ret){
        zlog_error(gZlog, "Failed to send heat beat to client[%d]! ret[%d]", client->fdCli, ret);
    }
    */
    /* Data transceiver thread */
    recv_and_parse(&link->chan);
    return ret;
}

int establishComm(linkDes_t *link)
{
    if (pthread_create(&link->commPth, NULL, (void *)commBindCliPth, link) != 0){
        zlog_error(gZlog, "Failed to create pthread [commBindCliPth]");
        return -1;
    }
    pthread_detach(link->commPth);
    //pthread_join(client->commPth, NULL);
    return 0;
}

linkList_t *linkListManage(linkList_t *head, typeListOp listOp, int fdClit, linkList_t *insNode)
{
    linkList_t *retNode     = NULL;
    linkList_t *crtNode     = NULL;
    linkList_t *inserNode   = NULL;
    linkList_t *delNode     = NULL;
    switch(listOp){
        case CREAT_LIST:
            head = (linkList_t *)malloc(sizeof(linkList_t));
            if(head){
                retNode = head;
            }
        break;
        case SELECT_LIST:
        if(head){
            crtNode = head;
            while(crtNode){
                if(crtNode->link.linkFd == fdClit)
                    break;
                crtNode = crtNode->next;
            }
            if(crtNode == NULL){
                retNode = NULL;
            }else{
                retNode = crtNode;
            }
        }
        break;
        case INSERT_LIST:/* always insert at tail */
            inserNode = (linkList_t *)malloc(sizeof(linkList_t));
            memcpy(inserNode, insNode, sizeof(linkList_t));
            inserNode->next = NULL;
            if(head){
                crtNode = head;
                while(crtNode->next){
                    crtNode = crtNode->next;
                }
                crtNode->next = inserNode;
                retNode = head;
            }
        break;
        case DELET_LIST:/* head, tail or middle */
        if(head){
            if(head->link.linkFd == fdClit){/* delet node is head */
                retNode = head->next;
                free(head);
                break;
            }
            crtNode = head;
            while(crtNode->next){/* delet node is not head */
                if(crtNode->next->link.linkFd == fdClit)
                    break;
                crtNode = crtNode->next;
            }
            if(crtNode->next){/* found */
                delNode = crtNode->next;
                crtNode->next = delNode->next;
                free(delNode);
            }
            retNode = head;
        }
        break;
        default:
        break;
    }
    return retNode;
}

int sizeOfclientList(linkList_t *head)
{
    int nodeCnt = 0;
    if(head){
        linkList_t *crtNode = head;
        while(crtNode){
            nodeCnt++;
            crtNode = crtNode->next;
        }
    }
    return nodeCnt;
}

void linkManagePth(void)
{
    unsigned int clientCnt      = 0;
    linkList_t clientNodeTemp   = {0};
    linkList_t *nodeTemp        = NULL;
    while(true){
        pthread_mutex_lock(&mutexGLinkSigInfo);
        clientNodeTemp.link = gLinkSigInfo.client;
        switch(gLinkSigInfo.linkSigType){
            case addLinkSig:{ 
                gLinkSigInfo.linkSigType = noLinkSig;
                if(clientCnt > 0){/* add a client to client the list */
                    if(!linkListManage(gClientListHead, INSERT_LIST, 0, &clientNodeTemp)){
                        zlog_error(gZlog, "Failed to insert clientListNode");
                    }
                }else{/* creat client list */
                    gClientListHead = linkListManage(NULL, CREAT_LIST, 0, NULL);
                    if(!gClientListHead){
                        zlog_error(gZlog, "Failed to create clientList");
                    }
                    memcpy(gClientListHead, &clientNodeTemp, sizeof(linkList_t));
                }
                /* select the current client from the client list and establish communication */
                if((nodeTemp = linkListManage(gClientListHead, SELECT_LIST, clientNodeTemp.link.linkFd, NULL)) != NULL){
                    if(establishComm(&nodeTemp->link) < 0){
                        zlog_error(gZlog, "Failed to establish communication pthread with client[%d]@[%s]:[%d]", \
                                clientNodeTemp.link.linkFd, clientNodeTemp.link.ipAddr, clientNodeTemp.link.port);
                    }
                    clientCnt ++;
                }
                pthread_mutex_unlock(&mutexGLinkSigInfo);
            }
            break;
            case deletLinkSig:{
                gLinkSigInfo.linkSigType = noLinkSig;
                if((nodeTemp = linkListManage(gClientListHead, SELECT_LIST, clientNodeTemp.link.linkFd, NULL)) != NULL){
                    zlog_info(gZlog, "deletLinkSig[%d]", nodeTemp->link.linkFd);
                    pthread_cancel(nodeTemp->link.commPth);
                    if (proChanDestroy(&nodeTemp->link.chan) < 0) {
                        zlog_error(gZlog, "Failed to destroy protocol channel");
                        break;
                    }
                    gClientListHead = linkListManage(gClientListHead, DELET_LIST, clientNodeTemp.link.linkFd, NULL);
                    clientCnt --;
                }
                pthread_mutex_unlock(&mutexGLinkSigInfo);
            }
            break;
            default:
                pthread_mutex_unlock(&mutexGLinkSigInfo);
            break;
        }/* end switch */
    }/* end while */
}

int linkManageInit(void)
{
    pthread_t linkPth;
    if (pthread_create(&linkPth, NULL, (void *)linkManagePth, NULL) != 0){
        zlog_error(gZlog, "Failed to create linkPth");
        return -1;
    }
    pthread_detach(linkPth);
    return 0;  
}

void linkSigPopPth(void *fun_pro_parse_exe)
{
    int fdServ           = -1;
    int dfClitemp        = -1;
    int portClitemp      = -1;
    char ipClitemp[16]   = {0};
    pthread_mutex_init(&mutexGLinkSigInfo, NULL);
    fdServ = sock_listen(DCM_PORT, NULL);
    if (fdServ < 0) {
        zlog_error(gZlog, "Failed to listen to port[%d]", DCM_PORT);
        return;
    }

    while (true) 
	{
        /* accept client */
        dfClitemp = sock_accept(fdServ, ipClitemp, sizeof(ipClitemp), &portClitemp);
        if (dfClitemp < 0){
            zlog_error(gZlog, "Failed to accept client, ret[%d]", dfClitemp);
            break;
        }
        pthread_mutex_lock(&mutexGLinkSigInfo);
        gLinkSigInfo.linkSigType           = addLinkSig;
        gLinkSigInfo.client.linkFd           = dfClitemp;
        gLinkSigInfo.client.port             = portClitemp;
        gLinkSigInfo.client.proParseExe      = fun_pro_parse_exe;
        memcpy(&gLinkSigInfo.client.ipAddr, ipClitemp, 16);
        pthread_mutex_unlock(&mutexGLinkSigInfo);
        zlog_info(gZlog, "Client[%d]@[%s:%d] accepted, fd[%d]", dfClitemp, ipClitemp, portClitemp, dfClitemp);
    }/* end while */
    return;
}

int linkSigInit(void *fun_pro_parse_exe)
{
    pthread_t linkSigPth;
    /* set SIGPIPE SIG_IGN */
    signal(SIGPIPE, SIG_IGN);
    if (pthread_create(&linkSigPth, NULL, (void *)linkSigPopPth, fun_pro_parse_exe) != 0){
        zlog_error(gZlog, "Failed to create pthread [linkSigPth]");
        return -1;
    }
    //pthread_detach(linkSigPth);
    pthread_join(linkSigPth, NULL);
    return 0;
}

int tcpServerInit(void *fun_pro_parse_exe)
{
    int ret = -1;
    /* a pthread for link manage */
    ret = linkManageInit();
    if(ret < 0){
        zlog_error(gZlog, "fail to init link manage");
    }
    /* a pthread for listen link signal */
    ret = linkSigInit(fun_pro_parse_exe);
    if(ret < 0){
        zlog_error(gZlog, "fail to init link signal");
    }
    return ret;
}

int connetToServer(char *serverIp, unsigned int port)
{
  int linkFd = -1;
  struct sockaddr_in serverSocket;

  if (linkFd != -1) {
    shutdown(linkFd, SHUT_RDWR);
    close(linkFd);
    linkFd = -1;
  }
  // 1.get socket
  linkFd = socket(AF_INET, SOCK_STREAM, 0);
  if (linkFd < 0) {
    zlog_error(gZlog, "initial tcp_client create socket failed\n");
    return -1;
  }
  // 2.socket noblock mode
  int flags = fcntl(linkFd, F_GETFL, 0);
  fcntl(linkFd, F_SETFL, flags | O_NONBLOCK);
  // 3.connect server
  bzero(&serverSocket, sizeof(serverSocket));
  serverSocket.sin_family = AF_INET;
  serverSocket.sin_addr.s_addr = inet_addr(serverIp);
  serverSocket.sin_port = htons(port);
  while (connect(linkFd, (struct sockaddr *)&serverSocket, sizeof(serverSocket)) == -1) {
    int circleTime = 0;
    ++circleTime;
    if (circleTime >= 20) {
      zlog_error(gZlog, "failed to connect to serverIp[%s], port[%d]", serverIp, port);
      return -1;
    }
    sleep(2);
    zlog_error(gZlog, "start to connect the host:%s\n", serverIp);
  }
  zlog_error(gZlog, "connect the host:%s success\n", serverIp);
  return linkFd;
}

int tcpDevConnectCtl(linkCtlInfo_t linkCtlInfo, void *proParseExe)
{
  int           ret               = -1;
  unsigned int  devCnt            = 0;
  linkList_t    serverNodeTemp    = {0};
  linkList_t    *nodeTemp         = NULL;
  serverNodeTemp.link             = linkCtlInfo.server;
  serverNodeTemp.link.proParseExe = proParseExe;
  switch(linkCtlInfo.linkCtlType){
    case ADD_LINK_CTL:{
      serverNodeTemp.link.linkFd = connetToServer(serverNodeTemp.link.ipAddr, serverNodeTemp.link.port);
      if(serverNodeTemp.link.linkFd < 0){
        zlog_error(gZlog, "faild to connect server device ip[%s], port[%d]", serverNodeTemp.link.ipAddr, serverNodeTemp.link.port);
      }
  		if(devCnt > 0){/* add a server to the list */
  			if(!linkListManage(gServerLinkHead, INSERT_LIST, 0, &serverNodeTemp)){
  				zlog_error(gZlog, "Failed to insert serverListNode");
  			}
  		}else{/* creat client list */
  			gServerLinkHead = linkListManage(NULL, CREAT_LIST, 0, NULL);
  			if(!gServerLinkHead){
  				zlog_error(gZlog, "Failed to create serverList");
  			}
  			memcpy(gServerLinkHead, &serverNodeTemp, sizeof(linkList_t));
  		}
  		/* select the current client from the client list and establish communication */
  		if((nodeTemp = linkListManage(gServerLinkHead, SELECT_LIST, serverNodeTemp.link.linkFd, NULL)) != NULL){
  			if(establishComm(&nodeTemp->link) < 0){
  				zlog_error(gZlog, "Failed to establish communication pthread with server[%d]@[%s]:[%d]", \
  						serverNodeTemp.link.linkFd, serverNodeTemp.link.ipAddr, serverNodeTemp.link.port);
  			}
  			devCnt ++;
  		}
  	}
  	break;
  	case DEL_LINK_CTL:{
  		if((nodeTemp = linkListManage(gServerLinkHead, SELECT_LIST, serverNodeTemp.link.linkFd, NULL)) != NULL){
  			zlog_info(gZlog, "deletLinkCtl[%d]", nodeTemp->link.linkFd);
  			pthread_cancel(nodeTemp->link.commPth);
  			if (proChanDestroy(&nodeTemp->link.chan) < 0) {
  				zlog_error(gZlog, "Failed to destroy protocol channel");
  				break;
  			}
  			gServerLinkHead = linkListManage(gServerLinkHead, DELET_LIST, serverNodeTemp.link.linkFd, NULL);
  			devCnt --;
  		}
  	}
  	break;
  	default:
  	break;
  }/* end switch */
}
